use std::collections::*;
use std::sync::{atomic, mpsc, Arc};
use std::{thread, time};

use craftdig_core::registrar;
use craftdig_datamodel::block::{action, action::Action};
use craftdig_datamodel::block::{BlkId, BlockStateSnapshot};
use craftdig_datamodel::message::MsgTyId;
use craftdig_datamodel::player::id::PlayerId;
use craftdig_datamodel::world::editable::Diff;
use craftdig_datamodel::world::environment::WorldEnv;
use craftdig_datamodel::world::map::WorldState;
use craftdig_datamodel::world::{ChunkState, WorldView};
use craftdig_math::*;
use craftdig_sim::clock::Clock;
use craftdig_sim::input::InputRing;
use craftdig_sim::phys::{compute_entity_physics_update, MoveIntent};
use craftdig_sim::tick::control::ServerMsg;
use craftdig_sim::tick::{
    self, config::BlockMsgIds, config::SimConfig, control::LoopIo, control::TickMsg,
};

use super::error::Error;
use crate::player::ControlInput;
use crate::raycast::{check_raycast, WorldRaycastHit};
use crate::SceneController;

/// Shared thread-local state.
pub struct ClientState {
    // todo
    init_time: atomic::AtomicU64,
    status: parking_lot::Mutex<ClientStatus>,
}

impl ClientState {
    pub fn init(&self) {
        let now = now_ts();
        self.init_time.store(now, atomic::Ordering::SeqCst);
    }

    pub fn set_status(&self, st: ClientStatus) {
        let mut status = self.status.lock();
        *status = st;
    }

    /// Gets the status of the client.
    pub fn get_status(&self) -> ClientStatus {
        self.status.lock().clone()
    }
}

/// State exposed to the game graphics/control thread.
pub struct ClientHandle {
    // Global client context.
    shared_state: Arc<ClientState>,
    registrar: Arc<registrar::GameRegistrar>,
    player_id: PlayerId,

    // World state.
    /// Clock we use for interpolation.
    clock: Clock,

    /// World environment we're currently in.
    world_env: WorldEnv,

    // Player input/output.
    /// Last player control inputs, if any.
    last_player_input: Option<ControlInput>,

    /// Actions that have been initiated but not acted on.
    input_ring: Arc<InputRing>,

    /// Pending changes to update graphically.
    output_queue: mpsc::Receiver<TickMsg>,

    // World state tracking.
    /// The index of the current tick that we're displaying on screen.
    ///
    /// This is the tick for the world state in `cur_world_state`.
    cur_rendered_tick: u64,

    /// Our local dirty copy of world state being displayed on-screen.
    dirty_world_state: Arc<WorldState>,

    /// Chunks we're written changes for but haven't emitted flags for yet.
    changed_chunks: BTreeSet<ChunkCoord>,

    /// Diff kept for updates applied before the world is ready.  Chunks are
    /// removed if they're marked as updated in an update message from the tick
    /// thread, then the remaining changes are reapplied.
    pending_diff: Diff,

    // World state tracking (new).
    /// Previous world state that we're interpolating *from*.
    prev_world_state: Arc<WorldState>,

    /// Current world state we're currently rendering or interpolating towards.
    cur_world_state: Arc<WorldState>,
}

impl ClientHandle {
    // General accessors.

    /// Returns a ref to the registrar.
    pub fn registrar(&self) -> &Arc<registrar::GameRegistrar> {
        &self.registrar
    }

    /// The ID of the player we're playing as.
    pub fn player_id(&self) -> &PlayerId {
        &self.player_id
    }

    /// Returns a ref to the dirty local copy of world state.
    pub fn dirty_world_state(&self) -> &Arc<WorldState> {
        &self.dirty_world_state
    }

    pub fn cur_world_state(&self) -> &Arc<WorldState> {
        &self.cur_world_state
    }

    pub fn pending_world_diff(&self) -> &Diff {
        &self.pending_diff
    }

    pub fn get_status(&self) -> ClientStatus {
        self.shared_state.get_status()
    }

    // High level control.

    /// Called by the bridge when the game actually starts loading.
    pub fn on_init(&self) {
        // TODO
        self.shared_state.set_status(ClientStatus::Running);
    }

    /// Fetches the next pending tick change, if there is one.  Guaranteed not
    /// to block.
    ///
    /// This *can* be called multiple times on the same frame if (for some
    /// reason) rendering a frame took multiple ticks or multiple ticks were fed
    /// in in quick succession.
    fn try_get_pending_tick_update(&self) -> Option<TickMsg> {
        self.output_queue.try_recv().ok()
    }

    // Player interaction

    /// Updates the stored player control input used for physics calculations.
    pub fn update_control_input(&mut self, inp: ControlInput) {
        self.last_player_input = Some(inp);
    }

    /// Submits an action to the client, to be wrapped up with context from the client.
    pub fn submit_player_action(&mut self, action_data: action::ActionData) {
        // Submit the action.
        self.apply_action_locally(&action_data);

        // Actually wrap up the action and submit it to the main thread.
        let action = Action::new(action::Source::Player(self.player_id), action_data);
        let mut inp_buf = self.input_ring.lock_cur_buffer();
        inp_buf.add_player_action(action);
    }

    /// Performs a raycast against the dirty world state, starting at one point
    /// and advancing towards the other in small steps.
    ///
    /// This is used for figuring out block selection.
    pub fn raycast_in_world(&self, start: Vec3f, end: Vec3f) -> Option<WorldRaycastHit> {
        let step_size = 0.01;
        check_raycast(
            start,
            end,
            step_size,
            self.dirty_world_state.as_ref(),
            &self.registrar,
        )
    }

    // Client rendering.

    /// Used to resync the tick thread state with the game state, acting on
    /// world/player updates.
    pub fn on_frame(&mut self, scene: &mut dyn SceneController) -> Result<(), Error> {
        // Immediately mark a few chunks as dirty.  This doesn't do neighbors
        // because we'll probably regenerate those in a tick or two anyways.
        for cc in self.changed_chunks.iter() {
            scene.flag_dirty_chunk(*cc);
        }

        // Now we've done everything.
        self.changed_chunks.clear();

        // Loop through any/all received messages first.
        let mut need_regen_dirty = false;
        while let Some(msg) = self.try_get_pending_tick_update() {
            need_regen_dirty = true;

            // Copy around the world fields.  This moves cur to prev and
            // overwrites the cur with the new value.
            std::mem::swap(&mut self.prev_world_state, &mut self.cur_world_state);
            self.cur_world_state = msg.state().clone();
            self.cur_rendered_tick = msg.tick_idx();

            let changes = msg.changes();

            // Process all the changed chunks.
            for cc in changes.changed_chunks() {
                // Mark it and its neighbors as dirty.
                // TODO keep enough data to figure out if we should actually
                // mark neighbors as dirty
                scene.flag_dirty_chunk(*cc);
                for d in Direction::array() {
                    scene.flag_dirty_chunk(cc.neighbor_to(d));
                }

                // Clear the chunk from the diff.
                self.pending_diff.reset_chunk(*cc);
            }

            // Print chat messages.
            for m in msg.msgs() {
                scene.show_chat_message(m.to_owned());
            }
        }

        // Calculate what our interpolation fraction is now.
        let interp = self
            .clock
            .get_target_partial_tick(self.cur_rendered_tick as i64, time::Instant::now());

        // Now if we replaced the world state we have to regenerate the dirty state.
        if need_regen_dirty {
            // TODO make the dirty state not be wrapped in an arc so we can just
            // modify it directly with the diff
            let (dirty_ws, _) = self.cur_world_state.apply_diff(self.pending_diff.clone());
            self.dirty_world_state = Arc::new(dirty_ws);
        }

        // Update interpolated positions.
        self.apply_world_frame_changes(interp, scene)?;

        Ok(())
    }

    // Internal implementation helpers.

    /// Applies per-frame updates to the world to do things like interpolating
    /// entity positions, including the player.
    fn apply_world_frame_changes(
        &mut self,
        interp: f32,
        scene: &mut dyn SceneController,
    ) -> Result<(), Error> {
        // TODO

        // Loop through all entities in the new world state.
        let cur_entities = self.cur_world_state.get_entity_ids();
        for eid in cur_entities {
            // Get the cur pos first so we can use it as a fallback if not
            // present in the prev world state.
            let new_pos = self
                .cur_world_state
                .get_entity_state(&eid)
                .map(|e| e.position())
                .expect("client: missing expected entity in current world state");
            let old_pos = self
                .prev_world_state
                .get_entity_state(&eid)
                .map(|e| e.position())
                .unwrap_or(new_pos);

            // Actually computing the interpolation is easy.
            let interp_pos = old_pos.lerp_to(new_pos, interp);
            eprintln!("entity {eid:?} interp pos {interp_pos}");
            scene.set_entity_position(eid, interp_pos);
        }

        // TODO handle entities that were removed

        Ok(())
    }

    /// Applies a player action to the dirty world state.
    fn apply_action_locally(&mut self, action_data: &action::ActionData) {
        use action::ActionData;
        match action_data {
            ActionData::BreakBlock { pos } => {
                let state = BlockStateSnapshot::new_simple(BlkId::zero());
                self.apply_dirty_block_change(*pos, &state);
            }
            ActionData::PlaceBlock { pos, new_state } => {
                self.apply_dirty_block_change(*pos, new_state);
            }
        }
    }

    fn apply_dirty_block_change(&mut self, pos: BlkCoord, state: &BlockStateSnapshot) {
        // FIXME this function is kinda clunky but it's fine
        // TODO rewrite to just directly modify an EditableWorld
        let (cc, scp) = pos.split();

        // If we haven't used the chunk yet, insert it.
        if !self.pending_diff.is_chunk_dirty(cc) {
            let data = self
                .cur_world_state
                .get_chunk_ref(cc)
                .cloned()
                .unwrap_or_else(|| ChunkState::new_full(BlkId::zero()));
            self.pending_diff.set_chunk(cc, data);
        }

        // Just set it.
        let cdata = self
            .pending_diff
            .get_chunk_mut(cc)
            .expect("client: missing newly inserted chunk in dirty state");
        cdata.set_block_state(scp, state);

        // Flag the chunk as changed.
        self.changed_chunks.insert(cc);
    }
}

/// Creates a client handle.  Will eventually start the tick thread.
pub fn launch(reg: Arc<registrar::GameRegistrar>) -> ClientHandle {
    let player_id = PlayerId::random();
    let init_world_state = Arc::new(WorldState::new_empty());
    let init_ws = init_world_state.clone();

    // FIXME don't hardcode
    let blk_msgs = BlockMsgIds::new(MsgTyId::from(0), MsgTyId::from(1), MsgTyId::from(2));
    let wprov =
        craftdig_worldgen::flatgrass::FlatgrassGen::new(0, 2, 3, 11, 14, reg.block_ty_reg())
            .expect("client: init worldprovider");
    let world_env = WorldEnv::new(0.5);
    let tick_dur = time::Duration::from_millis(500);

    let cfg = SimConfig::new(
        reg.clone(),
        blk_msgs,
        tick_dur,
        4,
        Arc::new(wprov),
        5,
        world_env.clone(),
    );

    // Construct the input communication channels.
    let inp_ring = Arc::new(InputRing::new(4));
    let (serv_inp_tx, serv_inp_rx) = mpsc::channel();
    let (output_tx, output_rx) = mpsc::channel();
    let loop_io = LoopIo::new(player_id, serv_inp_rx, inp_ring.clone(), output_tx);

    // Generate the player join event for ourselves so that we process it immediately.
    // TODO split this out into a separate controller handle thing
    let init_player_loc = Vec3f::new_y(3.0);
    let player_join_msg = ServerMsg::NewPlayer {
        id: player_id,
        loc: init_player_loc,
    };
    serv_inp_tx
        .send(player_join_msg)
        .expect("client: send player join");

    thread::spawn(move || {
        tick::tick_loop_main(init_world_state, loop_io, cfg);
    });

    ClientHandle {
        shared_state: Arc::new(ClientState {
            init_time: atomic::AtomicU64::new(0),
            status: parking_lot::Mutex::new(ClientStatus::Loading),
        }),
        registrar: reg,
        player_id,
        clock: Clock::new_now_at_0(tick_dur),
        world_env,
        last_player_input: None,
        input_ring: inp_ring,
        output_queue: output_rx,
        cur_rendered_tick: 0,
        dirty_world_state: init_ws.clone(),
        changed_chunks: BTreeSet::new(),
        pending_diff: Diff::new_empty(),
        cur_world_state: init_ws.clone(),
        prev_world_state: init_ws.clone(),
    }
}

/// Represents the sorta state of what the client is trying to do.
#[derive(Clone, Debug)]
pub enum ClientStatus {
    /// Currently loading into a world.
    Loading,

    /// Normal operation.
    Running,

    /// Stopping, possibly writing to disk.
    Stopping,

    /// Game stopped, flushed to disk.
    Stopped,

    /// Some unrecoverable error occured, with a message to show the user.
    Error(String),
}

impl ClientStatus {
    pub fn new_error<S: ToString>(s: S) -> Self {
        Self::Error(s.to_string())
    }
}

fn now_ts() -> u64 {
    time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64
}

#[deprecated]
fn make_player_aabb() -> AABB {
    craftdig_datamodel::player::model::make_player_aabb()
}
